package org.lc.cmd.connect.impl.ws;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jline.reader.ParsedLine;
import org.lc.cmd.CmdParam;
import org.lc.cmd.Command;
import org.lc.cmd.connect.ClientRequest;
import org.lc.cmd.connect.CmdClientConfig;
import org.lc.cmd.connect.impl.BaseClientSession;
import org.lc.cmd.connect.impl.util.JsonUtil;
import org.lc.cmd.connect.model.CloseInfo;
import org.lc.cmd.impl.ws.WebSocketSession;
import org.lc.cmd.model.CmdRequestData;
import org.lc.cmd.model.ConfirmData;
import org.lc.cmd.model.RequestFinishData;
import org.lc.cmd.model.WsData;

import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class WsSession extends WebSocketSession implements BaseClientSession {
    private final Map<String, WsRequest> requestMap = new ConcurrentHashMap<>();

    @Getter
    private final String id;

    @Getter
    private final String url;

    private Session session;

    public WsSession(String id, String url) {
        super(CmdClientConfig.defaultProvider);
        this.id = id;
        this.url = url;
    }

    @Override
    protected void sendData(WsData data) {
        session.getAsyncRemote().sendText(JsonUtil.toJson(data));
    }

    @Override
    public ClientRequest createRequest(CmdRequestData data) {
        WsRequest wsRequest = new WsRequest(this, data);
        requestMap.put(wsRequest.getId(), wsRequest);
        return wsRequest;
    }

    @ClientEndpoint
    public static class WsEndpoint {
        private static final Map<String, WsSession> map = new ConcurrentHashMap<>();

        @OnOpen
        public void onOpen(Session session) {
            // 处理连接打开事件
            log.info("open");
            System.out.println("Connected to server");
        }

        @OnMessage
        public void onMessage(String message, Session session) {
            log.info("message {}", message);
            // 处理收到的消息
            WsSession wsSession = map.get(session.getId());
            WsData data = JsonUtil.parse(message, WsData.class);
            switch (data.getType()) {
                case "request-finish":
                    RequestFinishData requestFinishData = JsonUtil.parse(data.getData(), RequestFinishData.class);
                    wsSession.requestMap.get(requestFinishData.getRequestId()).handleFinish(requestFinishData.getResult());
                    wsSession.requestMap.remove(requestFinishData.getRequestId());
                    break;
                case "request-confirm":
                    ConfirmData confirmData = JsonUtil.parse(data.getData(), ConfirmData.class);
                    wsSession.requestMap.get(confirmData.getRequestId()).handleConfirm(confirmData);
                default:
                    log.warn("unknown type: {}, data: {}", data.getType(), data.getData());
            }
        }

        @OnClose
        public void onClose(Session session, CloseReason reason) {
            WsSession wsSession = map.get(session.getId());
            // 为空时说明是主动删除，无需重试
            if (wsSession != null) {
                log.info("close, {}", reason);
                wsSession.reconnect(reason);
            }
        }

    }

    @Override
    public void connect() throws IOException {
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        try {
            session = container.connectToServer(WsEndpoint.class, URI.create(url));
            WsEndpoint.map.put(session.getId(), this);
        } catch (DeploymentException e) {
            throw new IOException(e);
        }
    }

    @Override
    public void close() throws IOException {
        if (!isOpen()) {
            return;
        }

        for (WsRequest request : requestMap.values()) {
            request.handleClose();
        }
        requestMap.clear();
        WsEndpoint.map.remove(session.getId());
        session.close();
    }

    @Override
    public boolean isOpen() {
        return session != null && session.isOpen();
    }

    @Override
    public List<Command> getEnabledCommands() {
        return null;
    }

    @Override
    public List<CmdParam> getEnabledParams(ParsedLine parsedLine) {
        return null;
    }

    /**
     * 重试
     *
     * @param closeReason 断联原因
     * @return 是否成功重连
     */
    public boolean reconnect(CloseReason closeReason) {
        CloseInfo closeInfo = new CloseInfo();
        closeInfo.setHasError(true);
        closeInfo.setMessage(String.format("[%d](%s)", closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase()));
        for (int i = 0; ; i++) {
            long delay = CmdClientConfig.reconnectStrategy.onRetry(this, closeInfo, i);
            if (delay < 0) {
                return false;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(delay);
                connect();
                CmdClientConfig.reconnectStrategy.onReconnected(this);
                return true;
            } catch (IOException | InterruptedException e) {
                closeInfo = new CloseInfo();
                closeInfo.setHasError(true);
                closeInfo.setMessage("建立连接失败：" + e.getMessage());
                closeInfo.setE(e);
            }
        }
    }
}
